#ifndef _CANARY2_HTTP_PARSER_H_
#define _CANARY2_HTTP_PARSER_H_

#include <llhttp.h>
#include <type_traits>
#include <utility>
#include <memory>
#include "parser_callbacks.h"
#include "request.h"
#include "request_builder.h"
#include "response_builder.h"
#include "../util/concepts.h"
#include <system_error> // ::std::error_code
#include "llhttp_error_category.h"

namespace wilson::http
{
    template<typename Messagebuilder>
    class basic_parser
    {
    public:
        using builder_type = Messagebuilder;
        using generate_type = typename builder_type::generate_type;
        using storage_type = typename builder_type::storage_type;

    public:
        basic_parser(storage_type&& stor)
            : basic_parser{ ::std::make_shared<storage_type>(::std::move(stor)) }
        {
        }

        basic_parser(::std::shared_ptr<storage_type> stor)
            : m_msg_builder{ stor }, 
              m_settings{ new ::llhttp_settings_t{} }, 
              m_internal_parser{ new ::llhttp_t{} }
        {
            ::llhttp_settings_init(m_settings);
            using this_type = ::std::remove_cvref_t<decltype(*this)>;

            m_settings->on_url                   = http::on_url<this_type>;
            m_settings->on_status                = http::on_status<this_type>;
            m_settings->on_header_field          = http::on_header_field<this_type>;
            m_settings->on_header_value          = http::on_header_value<this_type>;
            m_settings->on_body                  = http::on_body<this_type>;
            m_settings->on_message_begin         = http::on_message_begin<this_type>;
            m_settings->on_headers_complete      = http::on_headers_complete<this_type>;
            m_settings->on_message_complete      = http::on_message_complete<this_type>;
            m_settings->on_chunk_header          = http::on_chunk_header<this_type>;
            m_settings->on_chunk_complete        = http::on_chunk_complete<this_type>;
            m_settings->on_url_complete          = http::on_url_complete<this_type>;
            m_settings->on_status_complete       = http::on_status_complete<this_type>;
            m_settings->on_header_field_complete = http::on_header_field_complete<this_type>;
            m_settings->on_header_value_complete = http::on_header_value_complete<this_type>;
            
            ::llhttp_init(m_internal_parser, HTTP_BOTH, m_settings);
        }

        ~basic_parser() { delete m_internal_parser; delete m_settings; }

        basic_parser(basic_parser&& other) noexcept
            : m_settings{ ::std::exchange(other.m_settings, nullptr) },
              m_internal_parser{ ::std::exchange(other.m_internal_parser, nullptr) }
        {
        }

        basic_parser& operator=(basic_parser&& other) noexcept
        {
            m_settings          = ::std::exchange(other.m_settings, nullptr);
            m_internal_parser   = ::std::exchange(other.m_internal_parser, nullptr);
            return *this;
        }

        ::std::error_code execute()
        {
            m_internal_parser->data = this;
            auto& stor = m_msg_builder.storage();
            if (stor.blocks_size() != 1)
                stor.merge_to_unique();
            auto blk = stor.read_block(0);

            ::llhttp_errno_t err = ::llhttp_execute(
                m_internal_parser, 
                reinterpret_cast<const char*>(blk.data()), 
                blk.size()
            );
            if (err != HPE_PAUSED && err != HPE_OK)
            {
                return { err, llhttp_category() };
            }

            if constexpr (http::is_request<generate_type>)
            {
                m_msg_builder.set_method(
                    http::to_method(m_internal_parser->method)
                );
            }
            else 
            {
                m_msg_builder.set_status(
                    http::to_status(m_internal_parser->status_code)
                );
            }
            m_msg_builder.set_version(
                http::to_version(
                    m_internal_parser->http_major, 
                    m_internal_parser->http_minor
                ));
            ::llhttp_resume(m_internal_parser);

            err = ::llhttp_finish(m_internal_parser);
            if (err != HPE_OK)
            {
                if (err != HPE_INVALID_EOF_STATE)
                    return { err, llhttp_category() };
            }

            return {};
        }

        generate_type generate() { return builder().generate(); }
        auto& builder() noexcept { return m_msg_builder; }
        auto content_length() const noexcept 
            { return m_internal_parser->content_length; }

    private:
        ::llhttp_settings_t* m_settings{};
        ::llhttp_t* m_internal_parser{};
        builder_type m_msg_builder;
        
    public:
        /* Variable that provide to `on_header_field` function, 
         * because llhttp saperate the opertion of 
         * setting K/V pair into two functions,
         * so there need a trampoline variabable to help to
         * delay the actual assignment to the second function call
         * (`on_header_value`)
         */
        typename builder_type::string_type m_header_field_trampoline;
    };

    namespace server{ using request_parser  = basic_parser<request_builder>; }
    namespace client{ using response_parser = basic_parser<response_builder>; }
}

#endif
